#include <stdexcept>

#include "console.hpp"
#include "debug.hpp"
#include "script.hpp"

namespace lge2d
{

Console::Console() :
		m_font(0), m_window(0), m_script(0), m_history_scroll_dir(
				HistscrollNone), m_on_screen(false)
{
}

Console::Console(sf::RenderWindow * window, lge2d::Script * script,
		sf::Font * font) :
		m_font(font), m_window(window), m_script(script), m_history_scroll_dir(
				HistscrollNone)
{
}

void Console::Initialize()
{
	assert(NULL != m_window);
	assert(NULL != m_script);
	assert(NULL != m_font);

	m_result_text.SetFont(*m_font);
	int font_size = m_font->GetCharacterSize();
	m_result_text.SetSize((float) font_size);

	m_promt.SetFont(*m_font);
	m_promt.SetSize((float) font_size);
	m_promt.SetText(">");

	float pomt_y = (GetMaxVisibleLines() + 1) * font_size;
	m_promt.SetPosition(0.f, pomt_y);
	m_promt.SetColor(sf::Color(0, 255, 0));

	m_text.SetFont(*m_font);
	m_text.SetSize((float) font_size);
	m_text.SetText("");
	float x_pos = m_promt.GetRect().GetWidth();
	m_text.SetPosition(x_pos, pomt_y);

	m_text.SetColor(sf::Color(0, 255, 0));

	m_last_input.push_back("");

	sf::Color background_color(70, 70, 70, 125);
	sf::Vector2f left_bottom(m_window->GetWidth(), pomt_y + font_size * 1.3f);
	m_console_background = sf::Shape::Rectangle(sf::Vector2f(), left_bottom,
			background_color);
}
void Console::Update(float deltaTime)
{
	(void) 0;
}
void Console::AppendChar(sf::Uint32 ch)
{
	sf::Unicode::UTF32String str = m_text.GetText();
	str.push_back(ch);
	m_text.SetText(str);
}

void Console::EraseLastChar()
{
	sf::Unicode::UTF32String str = m_text.GetText();
	if (str.length() >= 1)
	{
		sf::Unicode::UTF32String str2 = str.substr(0, str.length() - 1);
		sf::Unicode::Text text(str2);
		m_text.SetText(text);
	}
}

struct ScipNewLineNTimes
{
	ScipNewLineNTimes(int scip_count) :
			m_scip_count(scip_count)
	{
	}
	bool operator()(std::string::value_type ch)
	{
		if (ch == '\n')
		{
			--m_scip_count;
			if (m_scip_count <= 0)
			{
				return true;
			}
		}
		return false;
	}
	int m_scip_count;
};

int Console::GetMaxVisibleLines()
{
	// get last N lines of result
	int window_half_height = m_window->GetHeight() / 2;
	int max_visible_lines = window_half_height
			/ (int) ((m_result_text.GetSize()));
	return max_visible_lines;
}

void Console::StoreLastInput()
{
	std::list<std::string>::iterator pos = std::find(m_last_input.begin(),
			m_last_input.end(), (std::string) m_text.GetText());
	if (pos != m_last_input.end())
	{
		sf::Unicode::Text text_tmp = *m_last_input.begin();
		*m_last_input.begin() = m_text.GetText();
		*pos = text_tmp;
	}
	else
	{
		m_last_input.push_front(m_text.GetText());
	}
}

void Console::ExecuteLineOfScript(const std::string& utf8)
{
	sf::Unicode::UTF8String utf8str = (sf::Unicode::Text) (utf8);
	std::string result = m_script->Execute((const char*) (utf8str.c_str()));
	// get last N lines of result
	std::string crop_result = GetLastNlinesOfResult(result);
	m_result_text.SetText(crop_result);
	StoreLastInput();
	m_text.SetText("");
}

bool Console::OnEvent(const sf::Event& event)
{
	bool utilize_event = false;
	if (event.Type == sf::Event::TextEntered)
	{
		utilize_event = OnTextEntered(event);
	}

	if (m_on_screen)
	{
		if (event.Type == sf::Event::KeyReleased
				&& event.Key.Code == sf::Key::Up)
		{
			OnKeyUp();
			utilize_event = true;
		}
		else if (event.Type == sf::Event::KeyReleased
				&& event.Key.Code == sf::Key::Down)
		{
			OnKeyDown();
			utilize_event = true;
		}
	}

	return utilize_event;
}

bool Console::OnTextEntered(const sf::Event& event)
{
	if (event.Text.Unicode == (sf::Uint32) '`')
	{
		m_on_screen = !m_on_screen;
		if (m_on_screen)
		{
			std::string script_output = m_script->GetOutput();
			std::string crop_str = GetLastNlinesOfResult(script_output);
			m_result_text.SetText(crop_str);
		}
	}

	if (m_on_screen)
	{
		if (event.Text.Unicode == static_cast<sf::Uint32>('\b')) // backspace
		{
			EraseLastChar();
		}
		else if (event.Text.Unicode == static_cast<sf::Uint32>('\r')) // return
		{
			ExecuteLineOfScript(m_text.GetText());
			m_history_scroll_dir = HistscrollNone;
		}
		else if (event.Text.Unicode != static_cast<sf::Uint32>('`'))
		{
			AppendChar(event.Text.Unicode);
		}
	}
	return m_on_screen;
}

void Console::OnKeyUp()
{
	if (!m_last_input.empty())
	{
		std::string last = *m_last_input.begin();
		if (m_history_scroll_dir == HistScrollDown)
		{
			m_last_input.pop_front();
			m_last_input.push_back(last);
			last = *m_last_input.begin();
		}

		m_history_scroll_dir = HistScrollUp;

		m_text.SetText(last);
		// Rotate list first to last
		m_last_input.pop_front();
		m_last_input.push_back(last);
	}
}

void Console::OnKeyDown()
{
	if (!m_last_input.empty())
	{
		std::string last = *(--m_last_input.end());
		if (m_history_scroll_dir == HistScrollUp)
		{
			m_last_input.push_front(last);
			m_last_input.pop_back();
			last = *(--m_last_input.end());
		}

		m_history_scroll_dir = HistScrollDown;

		m_text.SetText(last);
		// Rotate list last to first
		m_last_input.push_front(last);
		m_last_input.pop_back();
	}
}

std::string Console::GetLastNlinesOfResult(std::string& result)
{
	int max_visible_lines = GetMaxVisibleLines();
	int lines_in_result = std::count(result.begin(), result.end(), '\n');
	int skip_lines = 0;
	if (lines_in_result > max_visible_lines)
	{
		skip_lines = lines_in_result - max_visible_lines;
	}
	else if (lines_in_result < max_visible_lines)
	{
		int append_count = max_visible_lines - lines_in_result;
		std::string new_lines(append_count, '\n');
		result = new_lines + result;
	}

	std::string::iterator start_of_visible_text = std::find_if(result.begin(),
			result.end(), ScipNewLineNTimes(skip_lines));
	std::string crop_result(start_of_visible_text + 1, result.end());
	return crop_result;
}

void Console::Render() const
{
	if (m_on_screen)
	{
		m_window->Clear();
		m_window->Draw(m_console_background);
		m_window->Draw(m_result_text);
		m_window->Draw(m_promt);
		m_window->Draw(m_text);
	}
}

Console::~Console()
{
}

} // namespace lge2d
